package com.swn.common.util;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.Properties;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;

import org.apache.log4j.Logger;


/**
 * @ClassName	PropertiesTools
 * @Description	properties 文件工具类
 * @author		LiGang
 * @date		2015-11-27 下午 2:28:12
 * @change		2015-11-30 下午 5:05:51
 */
public final class PropertiesTools {
	
	private static Logger logger = Logger.getLogger("PropertiesTools.class");

    /**
     * 根据 给定的 key 值 和 fileName 的 property 文件名称 从当前资源库或者其父资源库中获取一个 String 字符串
     *
     * @Description TODO 	根据 给定的 key 值 从当前资源库或者其父资源库中获取一个 String 字符串
     * 						调用此方法 等价于 (String) {@link #getObject(java.lang.String) getObject}(key) 方法
     * 						参考  {@link #getBundle(String, Locale, ClassLoader) getBundle}
     * 						for a complete description of the search and instantiation strategy.
     *
     * @param	key 		String 要求的  String  类型的 键 key
     * @param	fileName 	String 要求的  String  类型  fileName 的无后缀名称
     * 						或者     fileName 全限定名 (无后缀名称)
     *
     * @exception	NullPointerException		如果 key == null					出现空指针异常
     * 											如果 fileName == null				出现空指针异常
     * @exception	MissingResourceException 	如果根据 key 没有找到对象				出现资源缺失异常
     * 											如果根据 fileName 没有获取到资源		出现资源缺失异常
     * @exception	ClassCastException 			如果根据 key 获取的对象不是 String 类型	出现类转型异常
     *
     * @return	String	返回根据 key 获取的 String 类型的资源
     */
    public static String getResourceAsString(String fileName, String key) {
    	logger.debug("reader properties file by key ... ");
    	//根据 fileName 得到资源库 	默认从本地加载资源
    	ResourceBundle bundle = ResourceBundle.getBundle(fileName);
    	return bundle.getString(key);
    }
    
    /**
     * 获取相对项目路径的属性文件key对应的值
     * @param path
     * @param key
     * @return
     */
	public static String getValueByKey(String path, String key) {
		try {
			// 获取绝对路径
			Properties props = new Properties();
			props.load(new InputStreamReader(PropertiesTools.class.getClassLoader().getResourceAsStream(path), "UTF-8"));
			String value = props.getProperty(key);
			return value;
		} catch (IOException e) {
			return null;
		}
	}
    

    /**
     * 根据 给定的 key 值 从根据 java.io.InputStream 对象创建的资源库中获取一个 String 字符串
     *
     * @Description TODO 	根据 给定的 key 值 从根据 InputStream 对象创建的资源库中获取一个 String 字符串
     * 						InputStream 的编码必须为 ISO-8859-1 编码
     * 						参考 (String) {@link #getObject(java.lang.String) getObject}(key) 方法
     *
     * @param	stream	java.io.InputStream 	从典型的property文件读取的单个字节流对象
     * @param	key		java.lang.String 		要求的  String  类型的 键 key
     *
     * @exception	IOException 				如果 I/O 出错						出现 I/O异常
     * @exception	NullPointerException 		如果 stream == null				出现空指针异常
     * 											如果 key == null					出现空指针异常
     * @exception	MissingResourceException 	如果根据 key 没有找到对象				出现资源缺失异常
     * @exception	ClassCastException 			如果根据 key 获取的对象不是 String 类型	出现类转型异常		
     */
    public static String getResourceAsString(InputStream stream, String key) throws IOException {
    	logger.debug("reader properties file by key ... ");
    	//根据 stream 得到资源库 InputStream 的编码必须为 ISO-8859-1 编码
    	ResourceBundle bundle = null;
		try {
			bundle = new PropertyResourceBundle(stream);
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (stream != null) stream.close();
		}
    	return bundle.getString(key);
    }

    /**
     * 根据 给定的 key 值 从根据 java.io.Reader 对象创建的资源库中获取一个 String 字符串
     *
     * @Description TODO	根据 给定的 key 值 从根据 java.io.Reader 对象创建的资源库中获取一个 String 字符串
     * 						java.io.Reader 对象的构造器无编码要求
     *
     * 						参考 {@link #PropertyResourceBundle(java.io.InputStream) PropertyResourceBundle(InputStream)},
     * 						这里对 properties 文件编码格式也没有要求
     *
     * @param		reader	java.io.Reader		从典型的property文件读取的单个字符流对象
     * @param		key		java.lang.String	要求的  String  类型的 键 key
     *
     * @throws		IOException 				如果 I/O 出错 						出现 I/O异常
     * @exception	NullPointerException 		如果 stream == null 				出现空指针异常
     * 											如果 key == null 					出现空指针异常
     * @exception	MissingResourceException 	如果根据 key 没有找到对象				出现资源缺失异常
     * @exception	ClassCastException 			如果根据 key 获取的对象不是 String 类型 出现类转型异常
     * @since 		1.6 	JDK	要求 	1.6	+
     */
    public static String getResourceAsString(Reader reader, String key) throws IOException {
    	logger.debug("reader properties file by key ... ");
    	//根据 reader 得到资源库	JDK	要求	1.6 +	构造器无编码要求
    	ResourceBundle bundle = null;
		try {
			bundle = new PropertyResourceBundle(reader);
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (reader != null) reader.close();
		}
    	return bundle.getString(key);
    }

    /**
     * 根据 java.io.Reader 对象获取创建的properties对象
     * 首先从字符输入流中读取一个property 列表(键值对形式) 字符输入流必须是一个格式化为单行的详细描述的键值对列表
	 *
	 * Properties 是被处理过的成行的形式  在这里有两种行形式:
	 * 1.自然行 2.逻辑行
	 *
	 * 自然行 ：是被定义为一行以终止字符结束每一行的行形式  (<code>\n</code> or <code>\r</code> or <code>\r\n</code>)
	 * 或者被流终止. 自然行可以是空行、注释行 或者持有一些或全部的键值对的行
	 *
	 * 逻辑行 ：行拥有所有的键值对数据 他们可以被扩展出 穿过 被反斜杠字符 <code>\</code>终止符号终止的的数个
	 * 邻近的自然行, 笔记这种注释行不能以这种方式扩展;  每个注释自然行必须有他们自己的指示器 来作为下面内容的描述.
	 * 行是从流的输入开始一直读到流的结束.
	 *
	 * 自然列只允许包含的空白区域是空字符来保证行不被干扰.  一个注释行包含一个 ASCII码  <code>'#'</code> or <code>'!'</code> 来作为他的空白区域的字符
	 * 注释行也不会被干扰 并且没有编码信息.
	 *
	 * 添加到行的休止符 格式会将字符区域的字符:
	 * 空格：	(<code>' '</code>, <code>'&#92;u0020'</code>),
	 * 制表符：	(<code>'\t'</code>, <code>'&#92;u0009'</code>),
	 * 换页符：	(<code>'\f'</code>, <code>'&#92;u000C'</code>)
	 * 转换为空白区间(区域)
	 *
	 * 如果一个逻辑行延伸穿过数个邻近的自然行,反斜杠转化行的休止符序列,行的休止符序列,和任何在下面行的空白区域不会对健或值元素产生影响.
	 *
	 * 键和元素解析时 (当加载时)
	 * 剩余部分的内容将会在单个自然行的行延伸子符被移除后,所有的字符将以组和的键值对呈现.
	 *
	 * 有没有充足的字符检查 会在行的休止符之前来确定 如果行的终止符被转译检查 将会有一些奇数的连续的反斜杠的
	 * 行休止符被转译从输入的开始一直处理到结束, 非零的偶数 2<i>n</i> 的连续反斜杠  行的终止符之前 (或其他处) 和反斜杠处理转译之后编译 <i>n</i>.
	 *
	 * 键包含字符的所在行的第一个并且最多一个非空白字符开始 , 但是不包括, 第一个并且非被转译的 <code>'='</code>, <code>':'</code>, or 空白字符的行的终止.
	 * 所有这些的键的终止字符都可以被包括在键被转译之前的反斜杠字符;比如,<code>\:\=</code>
	 *
	 * 将会有两个字符的键 <code>":="</code>.  行终止符可以被使用 <code>\r</code> 和 <code>\n</code> 的编码集包含.
	 * 任何键之后的空白都会被跳过; 如果键之后的第一个空白字符是 <code>'='</code> or <code>':'</code>, 那么将不会被干扰 并且在它之后的任何空白字符都会被跳过.
	 *
	 * 行内的剩余字符会变成联合的元素的字符串的行的一部分; 如果没有剩余字符, 元素会是空字符串 <code>&quot;&quot;</code>.
	 * 一旦行的字符序列由可识别的键-值组成, 转译处理作为以上内容的描述被执行.
	 *
	 * 做个例子, 下面列举的三个行的每个键<code>"Truth"</code> 和关联的元素的值 <code>"Beauty"</code>:
	 * Truth = Beauty 						(键后有空格 值前有空格)
	 *  Truth:Beauty						(键前有空格)
	 * Truth                    :Beauty 	(键后多空格)
	 *
	 * 另一个例子, 下面三行列举单个的 property:
	 * fruits                           apple, banana, pear, \
	 *                                  cantaloupe, watermelon, \
	 *                                  kiwi, mango
	 * 键是 <code>"fruits"</code>
	 * 联合的元素是:<pre>"apple, banana, pear, cantaloupe, watermelon, kiwi, mango"</pre>
	 *
	 * 说明：空白出现在了每个 <code>\</code> 之前所以在最终的结果一个空格会出现在每个空格之后;
	 * 这个 <code>\</code>,行休止符, 和在连续的行上的空白区间行距只是会被丢弃,并且没有被其他的一个或多个字符替换.
	 *
	 * 第三个例子, 这一行:
	 * 	cheeses
	 * 指定的键是 <code>"cheeses"</code> 关联的元素是空字符串:<code>""</code>.
	 *
	 * <a name="unicodeescapes"></a>
	 * 字符在键和值中可以被代表在它们的编码集相似的字符和字符串的字面值来使用
	 * 参考  <cite>The Java&trade; Language Specification</cite>)的 3.3章 和 3.10.6 节.
	 *
	 * 字符编码集和 Unicode 编码 在字符和字符串上的使用不同是:
	 *
	 * 1.	八进制编码是不被公认的
     *
     * 2.	字符集 <code>\b</code> 不代表退格字符
     *
     * 3.	方法不会将,<code>\</code> 视为一个字符, 不能有效的转译将会被视为一个error;
     * 		反斜杠在渐渐地减少  例如, 在 Java 字符串序列中<code>"\z"</code> 将会导致一个编译时异常.
     *
     * 		在比对中, 方法渐渐地减少反斜杠的使用.
     * 		因此, 方法会将两个字符序列<code>"\b"</code> 等价的看作为一个字符  <code>'b'</code>.
     *
     * 4.	转译对单引和双引号不是必须的;不管怎样, 根据上面的规定, 单引号和双引号字符
     * 		之前加上反斜杠仍然被视为单独的单引号和双引号字符.
     *
     * 5.	只有 单个'u'字符在Uniocde编码集中允许出现.
     *
	 * 参考 {@link #load(java.io.Reader) load(Reader)} 并且规定 编码格式为  ISO 8859-1 编码;
	 * 也就是说每个字符为一个拉丁字符.
	 * 非拉丁字符, 并且确定字符为键值对的形式表现 和 标签使用 Unicode 之外的内容定义在
	 * Java&trade; Language Specification 的 3.3 章节
	 *
	 *
     * @Description TODO 根据 java.io.Reader 对象获取创建的properties对象
     * @param reader	java.io.Reader  输入流对象.
     *
     * @exception 	NullPointerException 		如果 reader == null 			出现空指针异常
     * @throws  	IOException  				如果在从输入流读取的时候有错误发生	出现	IOException
     * @exception  	IllegalArgumentException 	如果输入流包含非法Unicode字符序列	出现非法参数异常	IllegalArgumentException
     *
     * @return Properties 方法执行之后返回加载完成的	Properties 对象
     * @since   	1.6							方法开始于JDK 1.6
     */
    public static Properties getProperties(Reader reader) throws IOException {
    	logger.debug("get properties file by reader ... ");
    	Properties properties = new Properties();
    	try {
    		//首先从字符输入流中读取一个property 列表（键值对形式） 字符输入流必须是一个格式化为单行的详细描述的键值对列表
			properties.load(reader);
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (reader != null) reader.close();
		}
		return properties;
    }

    /**
     * 根据 java.io.InputStream 对象获取创建的properties对象
     *
     * 首先从字节输入流中读取一个property 列表(键值对形式)
	 * 字节输入流必须是一个格式化为单行的详细说明的键值对列表
	 *
	 * 参考 {@link #load(java.io.Reader) load(Reader)} 并且规定 编码格式为 ISO 8859-1 编码;
	 * 也就是说每个字符为一个拉丁字符.
	 * 非拉丁字符, 并且确定字符为键值对的形式表现 和 标签使用 Unicode 之外的内容定义在
	 * Java&trade; Language Specification 的 3.3 章节
	 *
	 * 规定的流资源将会在方法执行之后加载
	 *
     * @Description TODO	根据 java.io.InputStream 对象获取创建的properties对象
     *						首先从字节输入流中读取一个property 列表(键值对形式)
     *						字节输入流必须是一个格式化为单行的详细说明的键值对列表
     *
     * @param	stream	java.io.InputStream  输入流对象.
     *
     * @exception	NullPointerException		如果 stream == null			出现空指针异常
     * @throws		IOException					如果在从输入流读取的时候有错误发生	出现	IOException
     * @exception	IllegalArgumentException	如果输入流包含非法Unicode字符序列	出现非法参数异常	IllegalArgumentException
     *
     * @return Properties	方法执行之后返回加载完成的	Properties 对象
     *
     * @since	1.2	方法开始于		JDK 1.2
     */
    public static Properties getProperties(InputStream stream) throws IOException {
    	logger.debug("get properties file by stream ... ");
    	//创建一个没有默认值的property列表
    	Properties properties = new Properties();
    	try {
    		//首先从字节输入流中读取一个property 列表(键值对形式)
    		//字节输入流必须是一个格式化为单行的详细说明的键值对列表
			properties.load(stream);
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (stream != null) stream.close();
		}
		return properties;
    }
    
    /**
     * @Description TODO 	根据 文件路径获取创建的properties对象
     *
     * @param	filePath	String	文件的权限定路径名
     *
     * @exception	NullPointerException		如果 filePath == null			出现空指针异常
     * @throws		FileNotFoundException 		如果文件路径无法找到				出现文件未发现异常	FileNotFoundException
     * @exception	IOException  				如果在从输入流读取的时候有错误发生	出现	IOException
     * @exception	IllegalArgumentException	如果输入流包含非法Unicode字符序列	出现非法参数异常	IllegalArgumentException
     *
     * @return Properties 方法执行之后返回加载完成的	Properties 对象
     * 
     */
    public static Properties getProperties(String filePath) throws FileNotFoundException, IOException {
    	logger.debug("get properties file by filePath ... ");
		return getProperties(new BufferedInputStream(new FileInputStream(filePath)));
    }
    
}
